﻿using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using corpus_platform.Data;
using System.Collections.ObjectModel;

namespace corpus_platform.ViewModels
{
    public class FrameViewModel : BindableBase, INodeStructure
    {
        public FrameViewModel(ListedFrame frame)
        {
            Root = frame.Root;
            Name = frame.Name;
            Information = frame.Information;
            parent = Root;
            foreach (ListedFrameElement listedFrameElement in frame.FrameElements)
            {
                if (listedFrameElement.IsSelected)
                {
                    FrameElement frameElement = listedFrameElement.FrameElement;
                    INodeFrameElement displayedFrameElement = null;
                    switch (frameElement.Type)
                    {
                        case FrameElementType.Boolean:
                            displayedFrameElement = new BoolFrameElementViewModel(frameElement);
                            break;
                        case FrameElementType.String:
                            displayedFrameElement = new StringFrameElementViewModel(frameElement);
                            break;
                        case FrameElementType.Single:
                            displayedFrameElement = new SingleFrameElementViewModel(frameElement);
                            break;
                        case FrameElementType.Multiple:
                            displayedFrameElement = new MultipleFrameElementViewModel(frameElement);
                            break;
                    }
                    displayedFrameElement.Frame = this;
                    FrameElements.Add(displayedFrameElement);
                }
            }
            Root.add(this);
        }

        public FrameViewModel(RootViewModel root, string name, string information, List<INodeFrameElement> frameElements, int? marker)
        {
            Root = root;
            this.parent = Root;
            Name = name;
            Information = information;
            FrameElements = frameElements;
            Marker = marker;
            IsInstance = true;
        }

        private INodeContainer parent;

        public StructureType Type => StructureType.Frame;
        public RootViewModel Root { get; }
        public string Name { get; }
        public string Information { get; }
        public List<INodeFrameElement> FrameElements { get; } = new();
        public int? Marker { get; set; } = null;

        private bool isInstance = false;
        public bool IsInstance
        {
            get { return isInstance; }
            set { SetProperty(ref isInstance, value); }
        }

        private bool allowDrag = true;
        public bool AllowDrag
        {
            get { return allowDrag; }
            set { SetProperty(ref allowDrag, value); }
        }

        private DelegateCommand commandCreateInstance;
        public DelegateCommand CommandCreateInstance =>
            commandCreateInstance ?? (commandCreateInstance = new DelegateCommand(ExecuteCommandCreateInstance, CanExecuteCommandCreateInstance));

        void ExecuteCommandCreateInstance()
        {
            Root.registerFrameInstance(this);
            foreach (INodeFrameElement frameElement in FrameElements)
            {
                frameElement.InformChangeFrameStatus();
            }
            this.IsInstance = true;
        }

        bool CanExecuteCommandCreateInstance()
        {
            foreach (INodeFrameElement frameElement in FrameElements)
            {
                if (frameElement.Type == FrameElementType.Single)
                {
                    SingleFrameElementViewModel singleFrameElementViewModel = (SingleFrameElementViewModel)frameElement;
                    if (singleFrameElementViewModel.Storage == null)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private DelegateCommand commandDecreateInstance;
        public DelegateCommand CommandDecreateInstance =>
            commandDecreateInstance ?? (commandDecreateInstance = new DelegateCommand(ExecuteCommandDecreateInstance, CanExecuteCommandDecreateInstance));

        void ExecuteCommandDecreateInstance()
        {
            Root.deregisterFrameInstance(this);
            foreach (INodeFrameElement frameElement in FrameElements)
            {
                frameElement.InformChangeFrameStatus();
            }
            this.IsInstance = false;
        }

        bool CanExecuteCommandDecreateInstance()
        {
            bool isNotElementOfInstance = false;
            if (parent is not RootViewModel)
            {
                INodeFrameElement frameElement = (INodeFrameElement)parent;
                if (frameElement.Frame.IsInstance == false)
                {
                    isNotElementOfInstance = true;
                }
            }
            else
            {
                isNotElementOfInstance = true;
            }
            return isNotElementOfInstance && Root.canDeregisterFrameInstance(this);
        }

        private DelegateCommand commandCopy;
        public DelegateCommand CommandCopy =>
            commandCopy ?? (commandCopy = new DelegateCommand(ExecuteCommandCopy));

        void ExecuteCommandCopy()
        {
            Root.copyFrameInstance(this);
        }

        private DelegateCommand commandRemove;
        public DelegateCommand CommandRemove =>
            commandRemove ?? (commandRemove = new DelegateCommand(ExecuteCommandRemove, CanExecuteCommandRemove));

        void ExecuteCommandRemove()
        {
            if (IsInstance)
            {
                parent.remove(this);
                Root.removeFrameInstance(this);
            }
            else
            {
                foreach (INodeFrameElement frameElement in FrameElements)
                {
                    switch (frameElement)
                    {
                        case SingleFrameElementViewModel:
                            SingleFrameElementViewModel singleFrameElementViewModel = (SingleFrameElementViewModel)frameElement;
                            singleFrameElementViewModel.SweepStorageToRoot();
                            break;
                        case MultipleFrameElementViewModel:
                            MultipleFrameElementViewModel multipleFrameElementViewModel = (MultipleFrameElementViewModel)frameElement;
                            multipleFrameElementViewModel.SweepStorageToRoot();
                            break;
                    }
                }
                parent.remove(this);
            }
        }

        bool CanExecuteCommandRemove()
        {
            if (parent is RootViewModel)
            {
                return true;
            }
            else
            {
                BaseFrameElementViewModel frameElement = (BaseFrameElementViewModel)parent;
                if (frameElement.Frame.IsInstance)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }

        public void ChangeParentTo(INodeContainer container)
        {
            if (container.add(this) == Result.Success)
            {
                parent.remove(this);
                parent = container;
            }
        }

        public FrameViewModel getClone()
        {
            return new FrameViewModel(Root, Name, Information, FrameElements, Marker);
        }

        public object GetValue(MetaInformation metaInformation)
        {
            if (!IsInstance)
            {
                return null;
            }
            else
            {
                FrameInstanceValue frameInstanceValue = new();
                frameInstanceValue.Frame = Name;
                frameInstanceValue.Marker = Marker;
                frameInstanceValue.Corpus = metaInformation.Corpus;
                frameInstanceValue.Source = metaInformation.Source;
                foreach (INodeFrameElement element in FrameElements)
                {
                    element.GetValue(metaInformation, frameInstanceValue);
                }
                return frameInstanceValue;
            }
        }
    }
}
